package com.atguigu.tingshu.search.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.pinyin.PinyinUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.Result;
import co.elastic.clients.elasticsearch._types.Script;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.Buckets;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.UpdateRequest;
import co.elastic.clients.elasticsearch.core.UpdateResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.album.AlbumFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.model.album.*;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.receiver.SearchReciever;
import com.atguigu.tingshu.search.repository.AlbumInfoIndexRepository;
import com.atguigu.tingshu.search.repository.SuggestIndexRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserFeignClient;
import com.atguigu.tingshu.vo.album.TrackStatMqVo;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import jakarta.annotation.Resource;
import jdk.dynalink.beans.StaticClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"all"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private AlbumInfoIndexRepository albumInfoIndexRepository;
    @Autowired
    private UserFeignClient userFeignClient;
    @Autowired
    private AlbumFeignClient albumFeignClient;
    @Autowired
    private ElasticsearchClient elasticsearchClient;
    @Autowired
    private SuggestIndexRepository suggestIndexRepository;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;

    /**
     * 期望单例Bean但找到多个，造成无法注入
     * 方式1：@Autowired 注入先按类型注入，在按照Bean名称注入
     * 方式2：@Autowired + @Qualifier 指定BeanID完成注入
     * 方式3：@Resource 先按Bean名称注入，在按类型注入
     */
    //@Autowired
    //@Qualifier("threadPoolTaskExecutor")
    @Resource(name = "threadPoolTaskExecutor")
    private Executor threadPoolTaskExecutor;

    /**
     * 将指定专辑上架到索引库
     *
     * @param albumId
     */
    @Override
    public void upperAlbum(Long albumId) {
        //创建用于封装索引数据对象
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
        CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            //调用远程接口获取专辑信息
            AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData();
            Assert.notNull(albumInfo, "专辑{}不存在！", albumId);
            //封装专辑信息
            BeanUtil.copyProperties(albumInfo, albumInfoIndex);
            List<AlbumAttributeValue> albumAttributeValueVoList = albumInfo.getAlbumAttributeValueVoList();
            if (CollectionUtil.isNotEmpty(albumAttributeValueVoList)) {
                List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueVoList.stream().map(vo -> BeanUtil.copyProperties(vo, AttributeValueIndex.class))
                        .collect(Collectors.toList());
                albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
            }
            return albumInfo;
        }, threadPoolTaskExecutor);

        CompletableFuture<Void> baseCategoryViewCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            //调用远程接口获取分类信息列表
            BaseCategoryView baseCategoryView = albumFeignClient.getCategoryView(albumInfo.getCategory3Id()).getData();
            Assert.notNull(baseCategoryView, "专辑{}所属分类{}不存在！", albumId);
            albumInfoIndex.setCategory1Id(baseCategoryView.getCategory1Id());
            albumInfoIndex.setCategory2Id(baseCategoryView.getCategory2Id());
        }, threadPoolTaskExecutor);

        CompletableFuture<Void> userInfoCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {

            //调用远程接口获取用户信息
            UserInfoVo userInfoVo = userFeignClient.getUserInfoVo(albumInfo.getUserId()).getData();
            Assert.notNull(userInfoVo, "用户{}不存在！", albumInfo.getUserId());
            albumInfoIndex.setAnnouncerName(userInfoVo.getNickname());
        }, threadPoolTaskExecutor);


        CompletableFuture<Void> statCompletableFuture = CompletableFuture.runAsync(() -> {

            //5.暂时随机为当前专辑生成四项统计数值，基于四项统计数值计算得出热度
            int num1 = RandomUtil.randomInt(2000, 3000);
            int num2 = RandomUtil.randomInt(1000, 2000);
            int num3 = RandomUtil.randomInt(500, 1000);
            int num4 = RandomUtil.randomInt(200, 500);
            albumInfoIndex.setPlayStatNum(num1);
            albumInfoIndex.setSubscribeStatNum(num2);
            albumInfoIndex.setBuyStatNum(num3);
            albumInfoIndex.setCommentStatNum(num4);
            //基于四项统计数值计算得出热度 规则：播放量*0.1 + 订阅量*0.2 + 购买量*0.3 + 评论量*0.4
            BigDecimal hotScore = new BigDecimal(num1).multiply(new BigDecimal("0.1"))
                    .add(new BigDecimal(num2).multiply(new BigDecimal("0.2")))
                    .add(new BigDecimal(num3).multiply(new BigDecimal("0.3")))
                    .add(new BigDecimal(num4).multiply(new BigDecimal("0.4")));
            albumInfoIndex.setHotScore(hotScore.doubleValue());
        }, threadPoolTaskExecutor);

        CompletableFuture.allOf(albumInfoCompletableFuture
                , baseCategoryViewCompletableFuture
                , userInfoCompletableFuture
                , statCompletableFuture).orTimeout(3, TimeUnit.SECONDS).join();

        //6.将数据保存到索引库
        albumInfoIndexRepository.save(albumInfoIndex);


        //7.将专辑标题存入提词索引库
        this.saveSuggestIndex(albumInfoIndex);

        //8.将专辑id存入布隆过滤器
        RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER);
        if (bloomFilter.isExists()){
            bloomFilter.add(albumId);
        }
    }

    /**
     * 将指定专辑下架索引库
     *
     * @param albumId
     */
    @Override
    public void lowerAlbum(Long albumId) {
        albumInfoIndexRepository.deleteById(albumId);
    }

    /**
     * 站内搜索
     *
     * @param albumIndexQuery
     * @return
     */
    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        try {
            //1.构建索引请求对象
            SearchRequest searchRequest = this.buildDSL(albumIndexQuery);
            System.err.println("本次检索DSL,复制到Kibana验证：");
            System.err.println(searchRequest.toString());
            //2.执行索引
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
            //3.解析响应结果
            return this.parseSearchResponse(searchResponse, albumIndexQuery);
        } catch (IOException e) {
            log.error("专辑检索信息失败：{}", e.getMessage());
            throw new RuntimeException(e);
        }
    }

    private static final String INDEX_NAME = "albuminfo";

    /**
     * 构建DSL
     *
     * @param albumIndexQuery
     * @return
     */
    @Override
    public SearchRequest buildDSL(AlbumIndexQuery albumIndexQuery) {
        //1.创建检索对象构建器对象
        SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();
        //1.设置索引名字
        searchRequestBuilder.index(INDEX_NAME);
        //2.设置查询条件 :关键字
        String keyword = albumIndexQuery.getKeyword();
        //3.逐项封装请求体参数
        //3.1 设置请求体参数"query"查询条件
        //3.1.1 创建bool查询组合三大查询条件
        BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();
        if (StringUtils.isNotBlank(keyword)) {
            boolQueryBuilder.must(m -> m.match(ma -> ma.field("albumTitle").query(keyword)));
        }
        //4.封装一级二级三级分类
        Long category1Id = albumIndexQuery.getCategory1Id();
        if (category1Id != null) {
            boolQueryBuilder.filter(f -> f.term(t -> t.field("category1Id").value(category1Id)));
        }
        Long category2Id = albumIndexQuery.getCategory2Id();
        if (category2Id != null) {
            boolQueryBuilder.filter(f -> f.term(t -> t.field("category2Id").value(category2Id)));
        }
        Long category3Id = albumIndexQuery.getCategory3Id();
        if (category3Id != null) {
            boolQueryBuilder.filter(f -> f.term(t -> t.field("category3Id").value(category3Id)));
        }
        //5.封装标签条件
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (CollectionUtil.isNotEmpty(attributeList)) {
            attributeList.forEach(attribute -> {
                String[] split = attribute.split(":");
                if (split != null && split.length == 2) {
                    boolQueryBuilder.filter(f -> f.nested(n -> n.path("attributeValueIndexList")
                            .query(q -> q.bool(b -> b.must(m -> m.term(t -> t.field("attributeValueIndexList.attributeId").value(split[0])))
                                    .must(m -> m.term(t -> t.field("attributeValueIndexList.valueId").value(split[1])))))));
                }
            });
        }


        searchRequestBuilder.query(boolQueryBuilder.build()._toQuery());

        //6.封装分页参数
        Integer from = (albumIndexQuery.getPageNo() - 1) * albumIndexQuery.getPageSize();
        Integer size = albumIndexQuery.getPageSize();
        searchRequestBuilder.from(from);
        searchRequestBuilder.size(size);

        //7.设置排序
        String order = albumIndexQuery.getOrder();
        if (StringUtils.isNotBlank(order)) {
            String[] split = order.split(":");
            if (split != null && split.length == 2) {
                //确定排序字段 以及 排序方向
                String orderField = "";
                switch (split[0]) {
                    case "1":
                        orderField = "hotScore";
                        break;
                    case "2":
                        orderField = "playStatNum";
                        break;
                    case "3":
                        orderField = "createTime";
                        break;
                }
                String finalOrderField = orderField;
                SortOrder sortOrder = split[1].equals("asc") ? SortOrder.Asc : SortOrder.Desc;
                searchRequestBuilder.sort(s -> s.field(f -> f.field(finalOrderField).order(sortOrder)));
            }
        }


        //8.设置高亮
        if (StringUtils.isNotBlank(keyword)) {
            searchRequestBuilder.highlight(h -> h.fields("albumTitle", f -> f.preTags("<font color='red'>")
                    .postTags("</font>")));
        }
        //9.设置返回字段
        searchRequestBuilder.source(s -> s.filter(f -> f.excludes("attributeValueIndexList"
                , "hotScore", "commentStatNum", "buyStatNum", "subscribeStatNum", "announcerName")));

        return searchRequestBuilder.build();
    }

    /**
     * 解析搜索结果
     *
     * @param searchResponse
     * @return
     */
    @Override
    public AlbumSearchResponseVo parseSearchResponse(SearchResponse<AlbumInfoIndex> searchResponse, AlbumIndexQuery albumIndexQuery) {
        //1.创建返回对象
        AlbumSearchResponseVo albumSearchResponseVo = new AlbumSearchResponseVo();
        //2.封装VO中分页相关四项属性
        //2.1当前页和每页记录数直接从参数中获取
        albumSearchResponseVo.setPageNo(albumIndexQuery.getPageNo());
        albumSearchResponseVo.setPageSize(albumIndexQuery.getPageSize());
        //2.2总记录数.从ES检索中的数据获取
        Long total = searchResponse.hits().total().value();
        albumSearchResponseVo.setTotal(total);
        //计算总页数
        Long totalPages = total % albumIndexQuery.getPageSize() == 0 ? total / albumIndexQuery.getPageSize() : total / albumIndexQuery.getPageSize() + 1;
        albumSearchResponseVo.setTotalPages(totalPages);

        //3.封装检索数据
        List<Hit<AlbumInfoIndex>> hits = searchResponse.hits().hits();
        if (CollectionUtil.isNotEmpty(hits)) {
            albumSearchResponseVo.setList(hits.stream().map(hit -> {
                AlbumInfoIndex albumInfoIndex = hit.source();
                if (CollectionUtil.isNotEmpty(hit.highlight())) {
                    albumInfoIndex.setAlbumTitle(hit.highlight().get("albumTitle").get(0));
                }
                return BeanUtil.copyProperties(albumInfoIndex, AlbumInfoIndexVo.class);
            }).collect(Collectors.toList()));
        }
        //4.返回
        return albumSearchResponseVo;
    }

    /**
     * 查询1级分类下置顶3级分类热度TOP6专辑
     *
     * @param category1Id
     * @return
     */
    @Override
    public List<Map<String, Object>> channel(Long category1Id) {
        try {
            //1.远程调用专辑服务，获取1级分类下7个置顶分类列表
            //1.1 发起远程调用
            List<BaseCategory3> baseCategory3List = albumFeignClient.findTopBaseCategory3(category1Id).getData();
            Assert.notNull(baseCategory3List, "没有查询到分类列表");
            Map<Long, BaseCategory3> category3Map = baseCategory3List.stream().collect(Collectors.toMap(BaseCategory3::getId, c3 -> c3));

            //1.3 基于3级分类集合转为FieldValue封装每个三级分类ID
            List<FieldValue> fieldValueList =
                    baseCategory3List.stream().map(c3 -> FieldValue.of(c3.getId())).collect(Collectors.toList());


            //2.检索ES获取每个置顶分类及包含热度前六专辑列表
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(
                    s -> s.index(INDEX_NAME).query(q -> q.terms(t -> t.field("category3Id").terms(t1 -> t1.value(fieldValueList))))
                            .aggregations("c3_aggs", a -> a.terms(t1 -> t1.field("category3Id").size(10))
                                    .aggregations("top6", a1 -> a1.topHits(t2 -> t2.size(6).sort(s1 -> s1.field(f -> f.field("hotScore")
                                                    .order(SortOrder.Desc)))
                                            .source(s1 -> s1.filter(f -> f.excludes("attributeValueIndexList",
                                                    "hotScore",
                                                    "commentStatNum",
                                                    "buyStatNum",
                                                    "subscribeStatNum",
                                                    "announcerName")))))).size(0), AlbumInfoIndex.class);

            //3.解析ES检索结果，封装数据并返回
            Map<String, Aggregate> aggregations = searchResponse.aggregations();
            if (CollectionUtil.isNotEmpty(aggregations)) {
                //3.1 将聚合结果转为LongTermsAggregate获取聚合buckets桶数组
                List<LongTermsBucket> bucketList = aggregations.get("c3_aggs").lterms().buckets().array();
                //3.2 遍历buckets桶数组，每遍历一次产出置顶分类热门专辑Map对象
                List<Map<String, Object>> mapList = bucketList.stream().map(bucket -> {
                    //封装置顶分类热门专辑Map对象
                    Map<String, Object> map = new HashMap<>();
                    //3.3 获取bucket的key
                    Long category3Id = bucket.key();
                    map.put("baseCategory3", category3Map.get(category3Id));

                    //封装top6专辑
                    List<AlbumInfoIndex> top6List = bucket.aggregations().get("top6").topHits()
                            .hits().hits().stream().map(hit -> {
                                //采用FastJson将专辑JSON转为索引库文档对象
                                return JSON.parseObject(hit.source().toString(), AlbumInfoIndex.class);
                            }).collect(Collectors.toList());
                    map.put("list", top6List);
                    return map;
                }).collect(Collectors.toList());
                return mapList;
            }
            return null;
        } catch (IOException e) {
            log.error("检索失败：{}", e.getMessage());
            throw new RuntimeException(e);
        }
    }

    /**
     * 将专辑标题存入提词索引库
     *
     * @param albumInfoIndex
     */
    @Override
    public void saveSuggestIndex(AlbumInfoIndex albumInfoIndex) {
        SuggestIndex suggestIndex = new SuggestIndex();
        suggestIndex.setId(albumInfoIndex.getId().toString());
        suggestIndex.setTitle(albumInfoIndex.getAlbumTitle());
        //用于汉字自动补全字段
        suggestIndex.setKeyword(new Completion(new String[]{albumInfoIndex.getAlbumTitle()}));
        //用于汉语拼音自动补全字段
        String pinyin = PinyinUtil.getPinyin(albumInfoIndex.getAlbumTitle(), "");
        suggestIndex.setKeywordPinyin(new Completion(new String[]{pinyin}));
        //用于汉字拼音首字母自动补全字段
        String pinyinSequence = PinyinUtil.getFirstLetter(albumInfoIndex.getAlbumTitle(), "");
        suggestIndex.setKeywordSequence(new Completion(new String[]{pinyinSequence}));

        //2.存入提词文档记录到提词索引库
        suggestIndexRepository.save(suggestIndex);
    }


    private static final String SUGGEST_INDEX = "suggestinfo";

    /**
     * 根据用户已录入字符查询提词索引库进行自动补全关键字
     *
     * @param keyword
     * @return
     */
    @Override
    public List<String> completeSuggest(String keyword) {
        try {
            //1.优先进行检索提示词索引库
            SearchResponse<SuggestIndex> searchResponse = elasticsearchClient.search(s -> s.index(SUGGEST_INDEX).suggest(s1 -> s1
                    .suggesters("keywordSuggest", s2 -> s2.prefix(keyword).completion(c -> c.field("keyword").skipDuplicates(true)))
                    .suggesters("pinyinSuggest", s2 -> s2.prefix(keyword).completion(c -> c.field("keywordPinyin").skipDuplicates(true)))
                    .suggesters("sequenceSuggest", s2 -> s2.prefix(keyword).completion(c -> c.field("keywordSequence").skipDuplicates(true)))
            ), SuggestIndex.class);

            //2.解析自动补全结果，将提示词列表放入hashset集合中(去重)
            HashSet<String> hashSet = new HashSet<>();
            hashSet.addAll(this.parseSuggestResult(searchResponse, "keywordSuggest"));
            hashSet.addAll(this.parseSuggestResult(searchResponse, "pinyinSuggest"));
            hashSet.addAll(this.parseSuggestResult(searchResponse, "sequenceSuggest"));

            //3.如果上面结果数量小于10个，尝试采用全文检索补齐到10个
            if (hashSet.size() < 10) {
                SearchResponse<AlbumInfoIndex> search = elasticsearchClient.search(
                        s -> s.index(INDEX_NAME)
                                .query(q -> q.match(m -> m.field("albumTitle").query(keyword)))
                                .source(s1 -> s1.filter(f -> f.includes("albumTitle")))
                                .size(10), AlbumInfoIndex.class);
                List<Hit<AlbumInfoIndex>> hitList = search.hits().hits();
                if (CollUtil.isNotEmpty(hitList)) {
                    for (Hit<AlbumInfoIndex> hit : hitList) {
                        String albumTitle = hit.source().getAlbumTitle();
                        hashSet.add(albumTitle);
                        if (hashSet.size() >= 10) {
                            break;
                        }
                    }
                }
            }
            //4.将所有提示词列表返回
            if (hashSet.size() >= 10) {
                return new ArrayList<>(hashSet).subList(0, 10);
            }
            return new ArrayList<>(hashSet);
        } catch (IOException e) {
            log.error("获取提示词列表失败：{}", e.getMessage());
            throw new RuntimeException(e);
        }
    }


    /**
     * 解析自动补全建议结果
     *
     * @param searchResponse 检索响应结果对象
     * @param suggestName    建议器名称
     * @return
     */
    @Override
    public Collection<String> parseSuggestResult(SearchResponse<SuggestIndex> searchResponse, String suggestName) {
        List<String> list = new ArrayList<>();
        List<Suggestion<SuggestIndex>> suggestionList = searchResponse.suggest().get(suggestName);
        if (CollUtil.isNotEmpty(suggestionList)) {
            for (Suggestion<SuggestIndex> suggestion : suggestionList) {
                for (CompletionSuggestOption<SuggestIndex> option : suggestion.completion().options()) {
                    String title = option.source().getTitle();
                    list.add(title);
                }
            }
        }
        return list;
    }


    /**
     * 更新专辑统计信息
     *
     * @param mqVo
     */
    @Override
    public void updateStatEs(TrackStatMqVo mqVo) {

        /**
         * UpdateRequest<Object, Object> request = UpdateRequest.of(u -> u
         *     .index("users")
         *     .id("user-456")
         *     .doc(new Object() {
         *         public String email = "new.email@example.com";
         *         public int age = 30;
         *     })
         * );
         */


        try {
            // 1. 创建更新请求
            UpdateRequest<AlbumInfoIndex, Object> request = UpdateRequest.of(u -> u
                    .index(INDEX_NAME)
                    .id(mqVo.getAlbumId().toString())
                    .script(createUpdateScript(mqVo.getCount()))
                    .retryOnConflict(3) // 冲突时重试3次
            );

            // 2. 执行更新 - 使用实际文档类型
            UpdateResponse<AlbumInfoIndex> response = elasticsearchClient.update(
                    request,
                    AlbumInfoIndex.class
            );

            // 3. 处理响应
            if (response.result() == Result.Updated) {
                System.out.println("播放量更新成功");
                AlbumInfoIndex updatedDoc = response.get().source();
                System.out.println("新播放量: " + updatedDoc.getPlayStatNum());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 创建更新脚本
    private Script createUpdateScript(int count) {
        return Script.of(s -> s
                .inline(i -> i
                        .source("ctx._source.playStatNum += params.count")
                        .params("count", JsonData.of(count))
                )
        );
    }


    /**
     * 更新播放专辑排行榜
     */
    @Override
    public void updateLatelyAlbumRanking() {
        //1.检索ES得到响应的TOP20专辑列表
        //1.1 远程调用专辑服务获取一级分类ID列表
        List<BaseCategory1> baseCategory1List = albumFeignClient.findAllCategory1().getData();
        Assert.notNull(baseCategory1List, "一级分类列表为空");
        List<Long> category1IdList = baseCategory1List.stream().map(BaseCategory1::getId)
                .collect(Collectors.toList());

        category1IdList.forEach(category1Id -> {
            //1.定义redis中的 key
            String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
            //1.2基于Key创建绑定hash操作对象
            BoundHashOperations hashOps = redisTemplate.boundHashOps(key);
            //处理某个一级分类排行数据，遍历5种不同排序字段
            String[] rankingDimensionArray
                    = new String[]{"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"};


            //2.根据1级分类ID+排序字段检索ES
            for (String dimension : rankingDimensionArray) {
                try {
                    SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(s -> s.index(INDEX_NAME)
                            .query(q -> q.term(t -> t.field("category1Id").value(category1Id)))
                            .sort(s1 -> s1.field(f -> f.field(dimension).order(SortOrder.Desc)))
                            .size(20)
                            .source(s1 -> s1.filter(f -> f.excludes("attributeValueIndexList",
                                    "hotScore",
                                    "commentStatNum",
                                    "buyStatNum",
                                    "subscribeStatNum",
                                    "announcerName"))), AlbumInfoIndex.class);
                    //3.解析响应结果

                    List<Hit<AlbumInfoIndex>> hits = searchResponse.hits().hits();
                    if (CollUtil.isNotEmpty(hits)) {
                        List<AlbumInfoIndex> albumInfoIndexList = hits.stream().map(hit -> hit.source()).collect(Collectors.toList());
                        hashOps.put(dimension, albumInfoIndexList);
                    }
                } catch (IOException e) {
                    log.error("更新播放专辑排行榜失败");
                    throw new RuntimeException(e);
                }
            };
        });
    }

    /**
     * 查询指定分类下指定维度的排行榜
     * @param category1Id
     * @param dimension
     * @return
     */
    @Override
    public List<AlbumInfoIndexVo> findRankingList(Long category1Id, String dimension) {
        String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
        BoundHashOperations<String, String, List<AlbumInfoIndex>> boundHashOperations = redisTemplate.boundHashOps(key);
        Boolean hassedKey = boundHashOperations.hasKey(dimension);
        if (hassedKey){
            List<AlbumInfoIndex> albumInfoIndexList = boundHashOperations.get(dimension);
            if (CollUtil.isNotEmpty(albumInfoIndexList)){
                List<AlbumInfoIndexVo> infoIndexVoList = albumInfoIndexList.stream().map(albumInfoIndex ->
                        BeanUtil.copyProperties(albumInfoIndex, AlbumInfoIndexVo.class)
                ).collect(Collectors.toList());
                return infoIndexVoList;
            }
        }
        return null ;
    }
}
